#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdarg.h>
#include <unistd.h>
#include <time.h>
#include <sys/time.h>
#include <errno.h>
#include <fcntl.h>
#include <mutex>

#include "conf.h"
#include "global.h"
#include "log.h"
#include "my_string.h"

// 互斥锁，保证线程安全
std::mutex m_mutex;

/**
 * 日志初始化，把日志文件打开
 * @return 是否成功打开日志文件（失败会被定位到标准输出）
 */
bool Log_init(const char* name){
    u_char *plogname = NULL;
    size_t nlen;

    // 从配置文件中读取和日志相关的配置信息
    CConfig *p_config = CConfig::GetInstance();
    plogname = (u_char *)p_config->GetString(name);
    if(plogname != NULL){
        // 只写打开|追加到末尾|文件不存在则创建【这个需要跟第三参数指定文件访问权限】
        g_log = open((const char *)plogname, O_WRONLY|O_APPEND|O_CREAT, 0644);
    }

    // 没有读取到有效路径或者日志文件打开失败就定位输出到屏幕
    if (g_log == -1){
        g_log = STDERR_FILENO;// 直接定位到标准输出（终端）
        Log_Error(errno, "无法打开 \"%s\" 日志文件！", plogname);
        return false;
    }
    return true;
}

/**
 * 真正的统筹信息写日志函数
 * @param level 日志等级，有ERROR和INFO两个
 * @param err 操作系统错误码
 * @param fmt 需要格式化的字符串
 * @param args 用于格式化的参数
 */
void Log_Print(int level, int err, const char *fmt, va_list args){
    std::unique_lock<std::mutex> lock(m_mutex);
    u_char errstr[MAX_ERROR_STR + 1];// 错误信息最大容量数组
    u_char *p,*last;
    last = errstr + MAX_ERROR_STR;// last指向整个buffer有效位置末尾
    memset(errstr,0,sizeof(errstr));// 重置，顺便充当字符串结束符

    // 1.拼接错误字符串
    p = Log_GetTime(errstr, last);// 获取时间相关
    const char* level_str = (level == LOG_ERROR) ? " [ERROR]:" : " [INFO]:";
    size_t len = strlen(level_str);
    p = cpymem(p, level_str, len);
    p = Slprintf(p, last, " %P ", g_pid);// 添加进程id
    // 字符串格式化
    p = Vslprintf(p, last, fmt, args);//将fmt和可变参数组合成字符串，写入p到last之间的缓冲区（字符串格式化）

    // 2.检查操作系统是否有错误（err保存了errno信息）
    if(err != 0) p = Log_errno(p, last, err);// 显示错误信息和代码
    // 如果位置不够，换行也要硬插入到末尾，哪怕覆盖到其他内容
    if(p >= (last - 1)) p = (last - 1) - 1;// 留出尾部空格
    *p++ = '\n'; // 增加一个换行符

    // 3.输出错误信息
    if(level == LOG_ERROR){// 正常情况下只有ERROR级的才输出到终端
        safe_write(STDERR_FILENO, errstr, p - errstr);// 往标准错误（一般是屏幕）输出信息
    }
    if(g_log != STDERR_FILENO){
        // 说明成功打开了日志文件，就也写入日志文件
        ssize_t n = write(g_log, errstr, p - errstr);
        // 如果写入失败且不是因为磁盘没空间
        if(n == -1 && errno != ENOSPC) {
            // 简化错误处理，避免递归
            std::string errorMsg = "日志写入失败: " + std::string(reinterpret_cast<char*>(errstr));
            safe_write(STDERR_FILENO, errorMsg.c_str(), errorMsg.length());
        }
    }
}

/**
 * 错误输出日志函数，会打印到标准输出，并写入日志文件（如果打开成功的话）
 * @param err 保存errno的错误码
 * @param fmt 保存需要格式化的字符串（包含%d等特殊符号）
 * @param ... 保存剩余参数
 */
void Log_Error(int err, const char *fmt, ...){
    va_list args;// va_list类型变量用来接收可变长参数
    va_start(args, fmt);
    Log_Print(LOG_ERROR, err, fmt, args);
    va_end(args);
}

/**
 * 向日志文件中写日志，自动添加换行符
 * @param err 保存errno的错误码
 * @param fmt 保存需要格式化的字符串（包含%d等特殊符号）
 * @param ... 保存剩余参数
 */
void Log_Info(int err, const char *fmt, ...){
    va_list args;// va_list类型变量用来接收可变长参数
    va_start(args, fmt);
    Log_Print(LOG_INFO, err, fmt, args);
    va_end(args);
}

/**
 * 拼接错误码和错误信息的函数
 * @param buf 写入的缓冲区开头
 * @param last 缓冲区末尾，避免溢出
 * @param err 操作系统的错误码
 */
u_char *Log_errno(u_char *buf, u_char *last, int err){
    char *perrorinfo = strerror(err);// 得到错误码对应的错误信息
    size_t len = strlen(perrorinfo);

    // 插入额外的字符串(%d:)
    char leftstr[20] = {0};
    snprintf(leftstr, sizeof(leftstr), " (%d: ", err); // 安全格式化
    size_t leftlen = strlen(leftstr);

    char rightstr[] = ") "; 
    size_t rightlen = strlen(rightstr);

    size_t extralen = leftlen + rightlen; //左右的额外宽度
    if ((buf + len + extralen) < last)
    {
        //保证整个放得下才放，否则就丢弃
        buf = cpymem(buf, leftstr, leftlen);
        buf = cpymem(buf, perrorinfo, len);
        buf = cpymem(buf, rightstr, rightlen);
    }
    // 最终呈现的效果（错误码: 错误信息）
    return buf;
}

/**
 * 获取时间字符串的函数
 * @return 格式如：2025/06/08 19:57:11
 */
u_char *Log_GetTime(u_char* buffer, u_char* last){
    // 获取时间相关
    struct timeval tv;// 包含秒数和时区
    // 详细时间结构体，秒 分 时 日 月 年 本周第几天 本年第几天 夏令时标志 
    struct tm tm;
    time_t sec;// 秒
    memset(&tv, 0, sizeof(struct timeval));
    memset(&tm, 0, sizeof(struct tm));

    gettimeofday(&tv, NULL);// 获取当前时间（秒数 + 时区）
    sec = tv.tv_sec;
    localtime_r(&sec, &tm);// 转换成本地时间（线程安全）
    tm.tm_mon++;// 月份要调整下正常
    tm.tm_year += 1900;// 年份要调整下才正常
    // 组合当前时间字符串，格式：2019/01/08 19:57:11
    return Slprintf(buffer, last, "%4d/%02d/%02d %02d:%02d:%02d", tm.tm_year, tm.tm_mon, tm.tm_mday, tm.tm_hour, tm.tm_min, tm.tm_sec);
}

/**
 * 安全的写入函数，避免被信号中断
 */
void safe_write(int fd, const void* buf, size_t count){
    const char* p = static_cast<const char*>(buf);
    while(count > 0) {
        ssize_t n = write(fd, p, count);
        if(n == -1) {
            if(errno == EINTR) continue; // 被信号中断，重试
            return;
        }
        p += n;
        count -= n;
    }
}













